package cn.demoncat.util.sec;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.demoncat.util.exception.IoRuntimeException;
import cn.demoncat.util.exec.ExecUtil;
import cn.demoncat.util.io.CloseUtil;
import cn.demoncat.util.io.FileUtil;
import cn.demoncat.util.io.IoUtil;
import cn.demoncat.util.lang.CollectionUtil;
import cn.demoncat.util.lang.IDUtil;
import cn.demoncat.util.lang.constant.SystemConstant;
import cn.demoncat.util.lang.entity.TypeList;
import cn.demoncat.util.sec.constant.SecurityConstant.KeyStoreType;
import cn.demoncat.util.sec.entity.CertInfo;
import cn.demoncat.util.sec.entity.CertParam;

/**
 * Java KeyTool工具
 * 
 * @author 延晓磊
 *
 * @since 2020年6月10日
 */
public class KeyToolUtil {

	private static final Logger LOG = LoggerFactory.getLogger(KeyToolUtil.class);
	
	
	/**
	 * 创建密钥库 - PKCS12格式(行业标准格式)
	 * 
	 * @param param		自签名证书参数
	 * @param out		输出流（x.p12/x.pfx，已关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月10日
	 */
	public static void out(CertParam param, OutputStream out) {
		out(KeyStoreType.PKCS12, param, out);
	}
	
	/**
	 * 创建密钥库 - JKS格式(Java默认专用)
	 * 
	 * @param param		自签名证书参数
	 * @param out		输出流（x.keystore，已关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月10日
	 */
	public static void outJks(CertParam param, OutputStream out) {
		out(KeyStoreType.JKS, param, out);
	}
	
	/**
	 * 创建密钥库
	 * 
	 * @param ksType	密钥库类型：KeyStoreType.JKS/PKCS12
	 * @param param		自签名证书参数
	 * @param out		输出流（已关闭）
	 * 
	 * @see <a href="https://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/keytool.html">keytool</a>
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月10日
	 */
	public static void out(String ksType, CertParam param, OutputStream out) {
		outByCommandLine(ksType, param, out);
	}
	
	/**
	 * 创建密钥库 >> 执行CMD命令，临时生成到本地，然后读取
	 * 
	 * @param ksType	密钥库类型：KeyStoreType.JKS/PKCS12
	 * @param param		自签名证书参数
	 * @param out		输出流（已关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月10日
	 */
	public static void outByCommandLine(String ksType, CertParam param, OutputStream out) {
		// 命令行：keytool -genkeypair
		List<String> lines = new ArrayList<>();
		lines.add("keytool");
		lines.add("-genkeypair");
		lines.add("-keyalg");
		lines.add("RSA");
		lines.add("-keysize");
		lines.add("2048");
		lines.add("-validity");
		lines.add(param.getValidity() + "");
		lines.add("-storetype");
		lines.add(ksType);
		lines.add("-alias");
		lines.add(param.getAlias());
		lines.add("-storepass");
		lines.add(param.getPwd());
		lines.add("-keypass");
		lines.add(param.getPwd());
		lines.add("-dname");
		lines.add(param.getOwner());
		lines.add("-keystore");
		String tmp = SystemConstant.DIR_TEMP + IDUtil.getNumTime();
		lines.add(tmp);
		// 执行命令，临时生成到本地
		TypeList<String> exec = ExecUtil.exec(CollectionUtil.toStrArray(lines));
		// 读取临时文件
		File file = new File(tmp);
		if (!file.exists()) {
			LOG.error("生成密钥库到临时文件失败：\r\n执行命令：{}\r\n执行详情：{}", lines, exec);
			throw new IoRuntimeException("生成密钥库失败");
		}
		// 输出密钥库
		IoUtil.copy(FileUtil.getIns(file), out);
		// 删除临时文件
		file.delete();
	}

	/**
	 * 创建密钥库 >> 使用sun.security包
	 * 
	 * @param ksType	密钥库类型：KeyStoreType.JKS/PKCS12
	 * @param param		自签名证书参数
	 * @param out		输出流（已关闭）
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月10日
	 */
	@SuppressWarnings("unused")
	private static void outBySunSecurity(String ksType, CertParam param, OutputStream out) {
		/* ====================================================  
		 * 弃用原因：
		 * 1、在JDK9以上会移除sun.security包
		 * 2、属于内部API，不保证稳定、不开放文档
		 * 3、Maven编译报错，需要配置POM
		 * 4、尽可能规避限制和过期的API，避免引起麻烦
		 * ===================================== =============== 
		 * 
		try {
			// 获取密钥库
	    	KeyStore ks = KeyStore.getInstance(ksType);
	    	// 加载密钥库（创建空密钥库）
	    	ks.load(null, null);
	    	// 创建密钥对
	    	sun.security.tools.keytool.CertAndKeyGen keypair = new sun.security.tools.keytool.CertAndKeyGen("RSA", "SHA256withRSA");
	    	keypair.generate(2048);
	    	// - 私钥
	    	PrivateKey privateKey = keypair.getPrivateKey();
	    	// - 证书链
	     	X509Certificate[] chain = new X509Certificate[]{keypair.getSelfCertificate(new sun.security.x509.X500Name(param.getCommonName(), param.getOrganizationalUnit(), param.getOrganization(), param.getCity(), param.getProvince(), param.getCountry()), new Date(), param.getValidity() * DateConstant.SECONDS_OF_DAY)};
	     	// - 保存
	     	char[] password = param.getPwd().toCharArray();
	        ks.setKeyEntry(param.getAlias(), privateKey, password, chain);
	        ks.store(out, password);
		} catch (Exception e) {
			throw new IoRuntimeException("生成密钥库失败", e);
		}finally {
			CloseUtil.close(out);
		}
		*/
	}
	
	/**
	 * 读取密钥库 - PKCS12格式(行业标准格式)
	 * 
	 * @param keyStore	密钥库文件（x.p12/x.pfx，已关闭）
	 * @param password	密码
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月6日
	 */
	public static CertInfo read(InputStream keyStore, String password) {
		return read(KeyStoreType.PKCS12, keyStore, password);
	}
	
	/**
	 * 读取密钥库 - JKS格式(Java默认专用)
	 * 
	 * @param keyStore	密钥库文件（x.keystore，已关闭）
	 * @param password	密码
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月6日
	 */
	public static CertInfo readJks(InputStream keyStore, String password) {
		return read(KeyStoreType.JKS, keyStore, password);
	}
	
	/**
	 * 读取密钥库 
	 * 
	 * @param ksType	密钥库类型：KeyStoreType.JKS/PKCS12
	 * @param keyStore	密钥库文件（已关闭）
	 * @param password	密码
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年6月6日
	 */
	public static CertInfo read(String ksType, InputStream keyStore, String password) {
		// 密码
		char[] pwd = password.toCharArray();
		// 读取KeyStore 
		CertInfo info = new CertInfo();
		try {
			// 加载密钥库
			KeyStore ks = KeyStore.getInstance(ksType);
			ks.load(keyStore, pwd);
			// 密钥库别名
			info.setAlias(ks.aliases().nextElement());
			// 私钥
			info.setPrivateKey((PrivateKey)(ks.getKey(info.getAlias(), pwd)));
			// 证书链
			info.setChain(ks.getCertificateChain(info.getAlias()));
			// 证书指纹
			info.setMd5(Md5Util.digest(info.getChain()[0].getEncoded()));
			return info;
		} catch (Exception e) {
			throw new IoRuntimeException("读取密钥库失败", e);
		}finally {
			CloseUtil.close(keyStore);
		}
	}
	
}
